/******************************************************************
文件名	：lcd.c
功能 	：用于实现lcd的一些显示和触摸功能
版本	：1.1
时间	：2017.10.24
作者	：王理洁
联系方式：wanglijie201710@163.com
******************************************************************/



#include "lcd.h"
  


unsigned int *fd_mem;
static 	 int fd_lcd;
unsigned int virtual_lcd[480][800];			//作为虚拟显存
/*******************************************************************
函数名	：	open_lcd
功能  	：	open "/dev/fb0" 即打开LCD设备,并使用mmap进行内存映射
参数  	：	无
返回值	：	0：打开及显存映射成功    -1：打开失败
*******************************************************************/
static int open_lcd(void)
{
	
	fd_lcd = open("/dev/fb0",O_RDWR);
	if(fd_lcd == -1)
	{
		perror("open LCD");
		return -1;
	}
	
	//显存映射
	fd_mem = mmap(NULL,800*480*4,PROT_READ | PROT_WRITE,MAP_SHARED,fd_lcd,0);
	if(fd_mem == MAP_FAILED)
	{
		perror("lcd mmap");
		return -1;
	}

	return 0;
}

/*******************************************************************
函数名	：	close_lcd
功能  	：	关闭LCD文件及其显存映射
参数  	：	无
返回值	：	无
*******************************************************************/
static void close_lcd(void)
{
	
    munmap(fd_mem,     //mmap映射后的地址,mmap的返回值  
           800*480*4);  //映射的大小 （字节）
	close(fd_lcd);		// release LCD
	
}


/*******************************************************************
函数名	：	draw_bmp
功能  	：	在LCD屏指定位置画一张图(写入虚拟显存，暂不显示)
参数  	：	unsigned short x					指定起始点x坐标
			unsigned short y					指定起始点y坐标
			const 	 char  *filename			图片的文件名
返回值	：0：写入成功    -1：写入失败
*******************************************************************/
int draw_bmp(int x,int y,const char *filename)
{	
	
	FILE *fd_bmp;
	char *buf;
	int ret;
	unsigned short i,j,k;
	BMPHEADER bmpheader;					//bmp header 结构体
	MAPHEADER mapheader;					//map header 结构体

	fd_bmp = fopen(filename,"r");			//打开bmp文件
	if(fd_bmp == NULL)
	{
		perror("show_picture:open bmp");
	}
	
		
	ret = fread(&bmpheader,14,1,fd_bmp);	//读bmpheader结构体
	if(ret != 1)
	{
		perror("show_picture:read bmpheader");
	}
	
	ret = fread(&mapheader,40,1,fd_bmp);	//读mapheader结构体
	if(ret != 1)
	{
		perror("show_picture:read mapheader");
	}
	//printf("显示位图：%s\n",filename);
	//printf("[draw_bmp_2:] 位图实际宽度（像素）:	%d\n",mapheader.biWidth);
	
	int map_width_byte = mapheader.biWidth*3;
	//printf("[draw_bmp_2:] 位图实际宽度（字节）:	%d\n",map_width_byte);
	
	int save_width_byte = (map_width_byte + 3) & (~3);	
	//printf("[draw_bmp_2:] 位图存储宽度（字节）:	%d\n",save_width_byte);
	//printf("\n\n");
	buf= malloc(save_width_byte);
	memset(buf,0,save_width_byte);
	
	for(i=0;i<mapheader.biHeight && y+i < 480;i++)				
	{	
		fseek( fd_bmp, -save_width_byte*(i+1), SEEK_END );
		
		fread(buf,save_width_byte,1,fd_bmp);
		
		for(k=0,j=0; k<mapheader.biWidth && x+k < 800; k++,j += 3)
			
		virtual_lcd[y+i][x+k]= (unsigned int)(buf[j] | buf[j+1] << 8 | buf[j+2] << 16);
	}
	
	fclose(fd_bmp);
	free(buf);
	buf = NULL;
	
	return 0;
}

/*******************************************************************
函数名	：	show_lcd_now
功能  	：	将虚拟显存显示出来
参数  	：	无
返回值	：	0：写入成功    
*******************************************************************/
int show_lcd_now(void)
{
	open_lcd();											//open LCD 
	memcpy(fd_mem,virtual_lcd,sizeof(virtual_lcd));	//将虚拟显存（数组unsigned int virtual_lcd[400][800]）的内容copy到LCD 显存
	close_lcd();										//关闭LCD
	return 0;
}

  


/*******************************************************************
函数名	：	draw_block
功能  	：	在LCD屏指定位置画一个指定大小和颜色的色块(写入虚拟显存，暂不显示)
参数  	：	lcd_block * block						块属性结构体指针
			typedef struct 
			{
				unsigned short 	x0;					//块左上角x坐标
				unsigned short 	y0;					//块左上角y坐标
				unsigned short 	width;				//块宽度
				unsigned short 	height;				//块高度
				unsigned int 	color;				//填充色
			}lcd_block;
返回值	：	0：写入成功    
*******************************************************************/
int draw_block(lcd_block * block)
{
	int vertical,horizontal;
	for(vertical = 0;vertical < block->height; vertical++ )
	{
		for(horizontal = 0; horizontal < block->width; horizontal++)
		{
			virtual_lcd[block->y0 + vertical][block->x0 + horizontal] = block->color;
		}
	}
	
	return 0;
}


/*******************************************************************
函数名	：	draw_box
功能  	：	在LCD屏指定位置画一个指定属性的矩形(写入虚拟显存，暂不显示)
参数  	：	lcd_box * box					矩形属性结构体
			typedef struct 
			{
				unsigned short 	x0;					//方框左上角x坐标
				unsigned short 	y0;					//方框左上角y坐标
				unsigned short 	width;				//方框宽度
				unsigned short 	height;				//方框高度
				unsigned short 	line_width;			//线宽，在方框的框线宽度
													//线宽向矩形内生长，矩形总大小由height，height两个参数指定
													//线完为0则没有框线
				unsigned int 	line_color;			//线色
				unsigned int 	fill_color;			//填充色
			}lcd_box;
返回值	：	0：写入成功    
*******************************************************************/
int draw_box(lcd_box * box)
{
	lcd_block block; 
		
	block.x0 		= box->x0;					//块左上角x坐标
	block.y0 		= box->y0;					//块左上角y坐标
	block.width 	= box->width;				//块宽度
	block.height	= box->height;				//块高度
	block.color	= box->fill_color;			//填充色
	draw_block(&block);		
	
	block.x0 		= box->x0;					//块左上角x坐标
	block.y0 		= box->y0;					//块左上角y坐标
	block.width 	= box->width;				//块宽度
	block.height	= box->line_width;			//块高度
	block.color	= box->line_color;			//填充色
	draw_block(&block);	

	block.x0 		= box->x0;					//块左上角x坐标
	block.y0 		= box->y0;					//块左上角y坐标
	block.width 	= box->line_width;			//块宽度
	block.height	= box->height;				//块高度
	block.color	= box->line_color;			//填充色
	draw_block(&block);		
	
	block.x0 		= box->x0 + box->width - box->line_width;	//块左上角x坐标
	block.y0 		= box->y0;									//块左上角y坐标
	block.width 	= box->line_width;							//块宽度
	block.height	= box->height;								//块高度
	block.color	= box->line_color;							//填充色
	draw_block(&block);		
	
	
	block.x0 		= box->x0;									//块左上角x坐标
	block.y0 		= box->y0 + box->height - box->line_width;	//块左上角y坐标
	block.width 	= box->width;								//块宽度
	block.height	= box->line_width;							//块高度
	block.color	= box->line_color;							//填充色
	draw_block(&block);	
	
	return 0;
}




/*******************************************************************
函数名	：	draw_pressed_button
功能  	：	把按键的颜色填充为反显色
参数  	：	lcd_button * button		目标button
返回值	：	original_color 			按钮的原始填充色
			
*******************************************************************/
static int draw_pressed_button(lcd_button * button)
{
	int  original_color = button->box.fill_color;
	button->box.fill_color = button->reverse_color; 
	draw_button(button);
	
	return original_color;
}
/*******************************************************************
函数名	：	press_button
功能  	：	阻塞等待button被按下，按下后对button进行反显，如果button有框线，框线不会返显，框线的宽度设为0，则无框线；
			松开按钮或触摸位置移动到按钮之外，调用按键指定的回调函数;
			第次调用本函数，只能响应按下的一个铵钮，没按下的按钮不响应；
			如果没有按到任何指定按钮，则继续等待
参数  	：	int num_of_button 					按键的个数（最少1个（不然你调用它干嘛），理论上无上限）
			lcd_button * button0				lcd_button 类型指针，指向button0
			void *(*button0_routine)(void *)	button0的回调函数（使用时注意格式，格式和pthread_create创建子线程的回调函数一样）
			void * button0_arg					button0加调函数的参数
			...									后面再加button,如上button0的格式，每个button要传入3个参数

返回值	：	0			
描述	：	注意：num_of_button是button的个数，不是传入参数的总个数			
*******************************************************************/
int press_button(int num_of_button,...)
{
	int original_color;									//用于存保按钮的原始填充色
	int tsfd;											//open触摸屏的文件描述符
	int arg_counter;									//用来计button的数量
	
	struct input_event key;								//触摸屏事件结构体
	struct touch_screen_data position;					//定议positon结构体，用于接收触屏返回值
	
	lcd_button * button;								//用来接收button们
	typedef void *(*routine)(void *);					
	routine button_routine;								//用来接收button的回调函数们
	void * button_arg;									//用来接收button的回调函数的参数们
	va_list pArgs;										//定义va_list类型的指针pArgs，用于存储参数地址
	
	tsfd = open("/dev/input/event0",O_RDONLY);			//open触摸屏
HEAD:	
	
	va_start(pArgs, num_of_button);						//初始化pArgs指针，使其指向第一个可变参数。该宏第二个参数是变参列表的前一个参数，即最后一个固定参数

	position = touch_screen();							//获得按下触摸屏的坐标
	for(arg_counter = 0;  arg_counter < num_of_button; arg_counter++)
	{
		button = va_arg(pArgs, lcd_button *);			//该宏返回变参列表中的当前变参值并使pArgs指向列表中的下个变参。该宏第二个参数是要返回的当前变参类型
														//若函数有多个可变参数，则依次调用va_arg宏获取各个变参
		button_routine 	= va_arg(pArgs, routine);	//接收回调函数
		button_arg 		= va_arg(pArgs, void *);	//接收回调函数的参数
		if	(	
				position.x > button->box.x0 						&& 
				position.x < button->box.x0 + button->box.width 	&&
				position.y > button->box.y0 						&&
				position.y < button->box.y0 + button->box.height
			)
		{
			original_color = draw_pressed_button(button);	//在虚拟显存上以反显色填充botton0,并记录其原始色
			show_lcd_now();									//把虚拟显存显示出来
				
			while(1)
			{
				read(tsfd,&key,sizeof(key));	
				if(key.type == EV_ABS && key.code == ABS_X )	
				{
					position.x = key.value;
				}
					
				if(key.type == EV_ABS && key.code == ABS_Y )	
				{
					position.y = key.value;
				}
				if(key.type == EV_KEY && key.code == BTN_TOUCH && key.value == 0)
				{
					break;
				}	
				if	(	
						position.x < button->box.x0 						|| 
						position.x > button->box.x0 + button->box.width 	||
						position.y < button->box.y0 						||
						position.y > button->box.y0 + button->box.height
					)
				{
					break;  
				}
					
				}
				button->box.fill_color = original_color; 
				draw_button(button);
				show_lcd_now();	
				
				
				button_routine(button_arg);
				goto EIXT;
		}

		
	}
	if(arg_counter == num_of_button)
	{
		va_end(pArgs);  //将指针pArgs置为无效，结束变参的获取
		printf("[press_button: ] 没按到任何按钮,再按一次吧老铁\n");
		goto HEAD;
	}

													
EIXT:	
	close(tsfd);
	va_end(pArgs);  //将指针pArgs置为无效，结束变参的获取
	return 0;
}
/**********************************************************
函数名	：	draw_button
功能	：	按照指定的属性在虚拟显存上画出button
输入	：	lcd_button * button		button属性结构体指针
输出	：	0
描述	：
**********************************************************/
int draw_button(lcd_button * button)
{
	draw_box(&button->box);
	if(button->text_flag)
		draw_text(&button->text);
	return 0;
}
/**********************************************************
函数名	：	draw_item
功能	：	按照指定的属性在虚拟显存上画出item
输入	：	lcd_button * button	button属性结构体
输出	：	0
描述	：
**********************************************************/
int draw_item(lcd_item * item)
{
	draw_block(&item->block);
	draw_block(&item->line);
	if(item->text_flag)
		draw_text(&item->text);
	return 0;
}
/**********************************************************
函数名	：	touch_screen
功能	：	阻塞等待，触碰到屏幕后返回触到点的坐标
输入	：	无
输出	：	position					坐标结构体
			struct touch_screen_data{
				int x;					x坐标
				int y;					y坐标
			};
描述	：
**********************************************************/
struct touch_screen_data touch_screen(void)
{
	int tsfd = open("/dev/input/event0",O_RDONLY);
	
	struct input_event key;
	struct touch_screen_data position;
	char x_flag = 0,y_flag = 0;
	
	while(1)
	{
		read(tsfd,&key,sizeof(key));
		if(key.type == EV_ABS && key.code == ABS_X )	
		{
			position.x = key.value;
			x_flag = 1;
			//printf("x = %d\n",position.x);
		}
		
		if(key.type == EV_ABS && key.code == ABS_Y )	
		{
			position.y = key.value;
			y_flag = 1;
			//printf("y = %d\n",position.y);
		}
			
		if(x_flag && y_flag)	break;
		
	}
	
	close(tsfd);
	return position;
}



/**********************************************************
函数名	：	cross_screen
功能	：	阻塞等待，从第一次触碰到屏幕开始读坐标，手指离开屏幕后返回
输入	：	无
输出	：	cross_data					划屏信息结构体
			struct cross_screen_data{
					int x1;			//第一次触碰到的x坐标
					int y1;			//第二次触碰到的y坐标
					int x2;			//最后一次触碰到的x坐标
					int y2;			//最后一次触碰到的y坐标
					int delta_x;	//x方向上的差值，正值为向右划，负值为向左划
					int delta_y;	//y方向上的差值，正值为向下划，负值为向上划
			};		
描述	：
**********************************************************/
struct cross_screen_data cross_screen(void)
{
	int tsfd = open("/dev/input/event0",O_RDONLY);
	
	struct input_event key;
	struct cross_screen_data  cross_data;
	char x1_flag = 0,y1_flag = 0,x2_flag = 0, y2_flag = 0;
	
	while(1)
	{
		read(tsfd,&key,sizeof(key));
		if(!x1_flag  || !y1_flag )
		{
			if(key.type == EV_ABS && key.code == ABS_X )	
			{
				cross_data.x1 = key.value;
				x1_flag = 1;
			}
			if(key.type == EV_ABS && key.code == ABS_Y )	
			{
				cross_data.y1 = key.value;
				y1_flag = 1;
			}
		}
		
		if(x1_flag && y1_flag)
		{
			
			if(key.type == EV_ABS && key.code == ABS_X)
			{
				cross_data.x2 = key.value;
				x2_flag = 1;
			}
			if(key.type == EV_ABS && key.code == ABS_Y)
			{
				cross_data.y2 = key.value;
				y2_flag = 1;
			}
		}
		
		if(x2_flag && y2_flag && key.type == EV_KEY && key.code == BTN_TOUCH && key.value == 0)
			{
				cross_data.delta_x = cross_data.x2 - cross_data.x1;
				cross_data.delta_y = cross_data.y2 - cross_data.y1;
				
				break; 
				
			}
	}
	close(tsfd);
	
	return cross_data;
}


/***************************************************************
函数名：draw_text
功能： 	在LCD的指定位置，打印指定的字
参数： 	
		typedef struct 
		{
			char 			type;				//字体			----传入ASCII码 ‘s’ ‘k’ ‘h’ ‘w’ 分别对应 仿宋，楷体，黑体，微软雅黑 
			unsigned char 	size;				//字号			----支持6个字号：16 24 32 40 48 56，16号既为16*16个像素点
			char * 			character;			//要打印的字串	----可以是英文或中文
			unsigned short  x;					//起始位置的X坐标
			unsigned short 	y;					//起始位置的Y坐标	
			unsigned int 	color_character;	//字体颜色		----传入 32bit ARGB 颜色值
			unsigned int 	color_background;	//字体背景颜色	----值为0xFFFFFFFF时背景为透明，不覆盖原来的内容
		}lcd_font;
返回值：默认返回0
***************************************************************/

int draw_text(lcd_text * text )
{	
	int i,j,k,n;
	u32 *char_code;
	u32 char_info = 0;
	u8 char_dot_maxtrix[56*56];
	u32 wide = 0,   cursor = 0;//,  position = 0;
	FILE *fd_fl;
	char filename[128]={0};
	FL_HEADER fl_header;
	
	sprintf(filename,"/fontlibrary/%c%d.bin",text->type,text->size);
	//printf("show_char: filename is : %s\n",filename);
	
	char_code = utf8_to_unicode(text->character);
		
	fd_fl = open_font_file(filename);
 	read_fl_header(&fl_header,fd_fl);
	
	cursor = wide = 0;
	for(n=0;char_code[n];n++)
	{	
		cursor += wide*8;
		char_info = read_char_info(char_code[n],&fl_header,fd_fl);
		memset(char_dot_maxtrix,0,sizeof(char_dot_maxtrix));
		read_char_dot_matix(char_info,char_dot_maxtrix,&fl_header,fd_fl);
		
		wide = (u8)((char_info >> 26)+7)/8;
		for(i = 0; i<fl_header.height; i++)
		{	
			for(j=0;j<wide;j++)
			{
				for(k=0;k<8;k++)
				{
					if((char_dot_maxtrix[j+i*wide] << k) & 0x80) 
							
						virtual_lcd[text->y + i][text->x + 8 * j + k +cursor] = text->color_character;	
					else if(text->color_background != 0xffffffff)
					{	
						virtual_lcd[text->y + i][text->x + 8 * j + k +cursor] = text->color_background;
					}
				}
			}	
		}
		
		
	}
	free(char_code);
	char_code = NULL;
	free(fl_header.psection);
	fl_header.psection = NULL;
	fclose(fd_fl);
	return 0;
}




























